/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */
package uk.ac.liv;


import java.io.BufferedWriter;
import java.io.FileWriter;
import javax.xml.parsers.*;
import org.xml.sax.*;
import org.w3c.dom.*;

import java.io.IOException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.HashMap;

/**
 *
 * @author jonesar
 */
public class ConvertSpectrum {
    
    
    private static String inputFile = "resources/myoC_spectrum.xml";
    private static String  outputFile = "outputFiles/myoC.mgf";
    
    private Document dom;
    
    private HashMap<Integer,String> lowEnergyPosToHeader = new HashMap();
    private HashMap<Integer,String> highEnergyPosToHeader = new HashMap();
    
    private HashMap<String,Integer> lowEnergyHeaderToPos = new HashMap();
    private HashMap<String,Integer> highEnergyHeaderToPos = new HashMap();
    
    private HashMap<String,String[]> lowEnergyData = new HashMap();  //ID --> Split data row map
    private HashMap<String,String[]> highEnergyData = new HashMap();
    
    private HashMap<String,String[]> parentIDsToFragmentIDs = new HashMap();
    
    private HashMap<String,Double> potentialMassesFromFasta = new HashMap();
    
    private ArrayList<String> parentIds = new ArrayList();
    
    private static Double HMASS =1.007825;
    private static Double OMASS = 15.994915;
    
    /**
     * @param args the command line arguments
     */
    public static void main(String[] args) {
        // TODO code application logic here
        
        ConvertSpectrum convert = new ConvertSpectrum();
            
    
    }
    
     /** Constructor for testing only
     * @param args the command line arguments
     */
    public ConvertSpectrum(){
        init();     //run with defaults
    }
    
    public ConvertSpectrum(String inFile, String outFile){
        inputFile = inFile;
        outputFile = outFile;
        init();
      
    }
    
    
    private void init(){
        parseXmlFile(inputFile);
        parseDocument();
        writeMGFFile(outputFile);
        
 //       ProcessFasta pf = new ProcessFasta();
 //       potentialMassesFromFasta = pf.getAllPeptideMasses();
 //       searchForCrosslinkedPeptides();
    }
    
    
    private void parseXmlFile(String file){
		//get the factory
        //System.out.println("Parsing XML");
        DocumentBuilderFactory dbf = DocumentBuilderFactory.newInstance();

        try {

                //Using factory get an instance of document builder
                DocumentBuilder db = dbf.newDocumentBuilder();

                //parse using builder to get DOM representation of the XML file
                dom = db.parse(file);

 

        }catch(ParserConfigurationException pce) {
                pce.printStackTrace();
        }catch(SAXException se) {
                se.printStackTrace();
        }catch(IOException ioe) {
                ioe.printStackTrace();
        }
    }
    
    private void parseDocument(){
        
        //System.out.println("Parsing doc");
        
        //get the root element
        Element docEle = dom.getDocumentElement();

        NodeList nl = docEle.getElementsByTagName("FORMAT");
        
        if(nl != null && nl.getLength() > 0) {
            for(int i = 0 ; i < nl.getLength();i++) {

                //get the employee element
                Element el = (Element)nl.item(i);
                
                int fragLevel = Integer.parseInt(el.getAttribute("FRAGMENTATION_LEVEL"));
                if(fragLevel==0){
                    processDataHeaders(el,fragLevel);
                }
                else if(fragLevel==1){
                    processDataHeaders(el,fragLevel);
                }
                else{
                    System.out.println("Error fragmentation level not recognized: " + fragLevel);
                }
            }
        }
        
        //get a nodelist of elements
        nl = docEle.getElementsByTagName("DATA");
        if(nl != null && nl.getLength() > 0) {
            Element el = (Element)nl.item(0);    
            String data = el.getFirstChild().getNodeValue();
            processSpectralData(data,0);           
        }
        
        nl = docEle.getElementsByTagName("HE_DATA");
        if(nl != null && nl.getLength() > 0) {
            Element el = (Element)nl.item(0);    
            String data = el.getFirstChild().getNodeValue();
            processSpectralData(data,1);           
        }
        
        nl = docEle.getElementsByTagName("PRECURSOR_BIN");
        joinParentsToFragments(nl);
        
    }
    
    
    private void processDataHeaders(Element formatElement, int fragLevel){
        
        /*
         *     <FIELD POSITION="1" NAME="Mass" />
         *     <FIELD POSITION="2" NAME="Intensity" />
         */
        NodeList nl = formatElement.getElementsByTagName("FIELD");
        
        if(nl != null && nl.getLength() > 0) {
            for(int i = 0 ; i < nl.getLength();i++) {
                Element el = (Element)nl.item(i);
                
                int headerPos = Integer.parseInt(el.getAttribute("POSITION"))-1;        //Correct by 1 to start numbering at zero
                String headerName = el.getAttribute("NAME");
                
                //System.out.println(headerPos + "=>" + headerName);
                
                if(fragLevel == 0){
                    
                    lowEnergyPosToHeader.put(headerPos,headerName);
                    lowEnergyHeaderToPos.put(headerName,headerPos);
                }
                else if(fragLevel == 1){
                    highEnergyPosToHeader.put(headerPos,headerName);
                    highEnergyHeaderToPos.put(headerName,headerPos);
                }
                else{
                    System.out.println("Error frag level not recognized... :" + fragLevel);
                }
            }
        }
        
    }
    

    private void processSpectralData(String data, int fragLevel){
        
        String[] peakDataPoints = data.split("\n");
        
        HashMap<Integer,String> headers;
        
        if(fragLevel ==0){
            headers = lowEnergyPosToHeader;
        }
        else{
            headers = highEnergyPosToHeader;
        }
        
        for(String peakRow : peakDataPoints){
            
            
            peakRow = peakRow.trim();     //Some whitespace on the beginning of the row
                        
            if(peakRow != null && !peakRow.equals("")){
                //System.out.println("Row:" + peakRow + "end");
                String[] peakCell = peakRow.split(" ");

                if(fragLevel ==0){
                    int idPos = lowEnergyHeaderToPos.get("LE_ID");                    
                    lowEnergyData.put(peakCell[idPos], peakCell);
                }
                else if(fragLevel ==1){
                    int idPos = highEnergyHeaderToPos.get("HE_ID");
                    highEnergyData.put(peakCell[idPos], peakCell);
                }
                else{
                    System.out.println("Error frag level not recognized: " + fragLevel);
                }
                
                
            }
            
        }       
        
        
    }
    
    
    private void joinParentsToFragments(NodeList nl){
        
        //parentIDsToFragmentIDs
        //<PRECURSOR_BIN LE_ID="2" HE_ID="105,106,107" />
        if(nl != null && nl.getLength() > 0) {
            for(int i = 0 ; i < nl.getLength();i++) {
                Element el = (Element)nl.item(i);
                
                String parentID = el.getAttribute("LE_ID");
                String[] fragmentIDs = el.getAttribute("HE_ID").split(",");
                
                parentIDsToFragmentIDs.put(parentID,fragmentIDs);
                parentIds.add(parentID);                                //To keep ordering of parents by position in the file (i.e. ID)
                //System.out.println(parentID + "==>" + Arrays.toString(fragmentIDs));
            }
        }
    }
    
    
    private void writeMGFFile(String outFile){
        try{
            // Create file 
            FileWriter fstream = new FileWriter(outFile);
            BufferedWriter out = new BufferedWriter(fstream);
            
            for(String parentId : parentIds){
                
                
                String[] parentData = lowEnergyData.get(parentId);
                String[] fragmentIDs = parentIDsToFragmentIDs.get(parentId);
                
                //System.out.println("Parent: " + parentId + "data:" + Arrays.toString(parentData));

                String mass = parentData[lowEnergyHeaderToPos.get("Mass")];
                int charge = Integer.parseInt(parentData[lowEnergyHeaderToPos.get("Z")]);
                Double mz = (Double.parseDouble(mass) -HMASS + (charge*HMASS))/charge;             //They give MH, we need to remove one hydrogen, then add charge * hydrogen, then divide by charge
                //Double mz = (Double.parseDouble(mass))/charge; 
                //688.4114	1374.8082	1377.8344	-3.0262	0	94	3.2e-006	+1	U	K.HGTVVLTALGGILK.K
                //Observed	Mr(expt)	Mr(calc)
                String intensity = parentData[lowEnergyHeaderToPos.get("Intensity")];
                String rt = parentData[lowEnergyHeaderToPos.get("RT")];
                
                
                out.write("BEGIN IONS\n");
                out.write("TITLE=CL_" + mz + "_" + rt + "\n");
                out.write("CHARGE=" + charge + "\n");
                out.write("RTINSECONDS=" + rt + "\n");
                out.write("PEPMASS="+ mz + "\n");            //Difficult to work out whether mass in spectrum is the same as intended for MGF
                //In Mascot generic format, the precursor peptide mass is an observed m/z value, from which Mr or MHnn+ is calculated using the prevailing charge state.
                
                for(String fragmentID : fragmentIDs){
                    
                    String[] fragmentData = highEnergyData.get(fragmentID);
                    String fragmentMass = fragmentData[highEnergyHeaderToPos.get("Mass")];
                    int fragmentCharge = Integer.parseInt(fragmentData[highEnergyHeaderToPos.get("Z")]);
                    Double fragmentMZ = (Double.parseDouble(fragmentMass) -HMASS + (fragmentCharge*HMASS))/fragmentCharge; 
                    String fragmentIntensity = fragmentData[highEnergyHeaderToPos.get("Intensity")];
                    out.write(fragmentMZ + " " + fragmentIntensity + "\n");                  
                            
                }
                out.write("END IONS\n\n");                
            }
            
            
            //Close the output stream
            out.close();
            
            
            System.out.println("Output written to " + outFile);
        }catch (Exception e){//Catch exception if any
            System.err.println("Error: " + e.getMessage());
            e.printStackTrace();
        }
        
    }
    
    /*
     * Basic search based on MS1 mass
     * 
     */
    private void searchForCrosslinkedPeptides(){
        
        
        for(String parentId : parentIds){                
                
            String[] parentData = lowEnergyData.get(parentId);
            String[] fragmentIDs = parentIDsToFragmentIDs.get(parentId);


            Double pepMass = Double.parseDouble(parentData[lowEnergyHeaderToPos.get("Mass")]) - HMASS;      //The spectrum contains MH - remove one H, to get pep Mass   
            
            Double tol = 0.015;
            
            //System.out.println(pepMass);
            
            for(String theoPep : potentialMassesFromFasta.keySet()){
                
                Double theoMass = potentialMassesFromFasta.get(theoPep);
                
                if(pepMass > theoMass -tol &&  pepMass < theoMass +tol){
                    
                    System.out.println(theoPep + "," + (theoMass+1) + "," + Arrays.toString(parentData).replace("[","").replace("]",""));
                }
                
                
            }
            
            
            
        }        
        
    }
    
    
    
    /**
        * I take a xml element and the tag name, look for the tag and get
        * the text content
        * i.e for <employee><name>John</name></employee> xml snippet if
        * the Element points to employee node and tagName is 'name' I will return John
        */
    private String getTextValue(Element ele, String tagName) {
            String textVal = null;
            NodeList nl = ele.getElementsByTagName(tagName);
            if(nl != null && nl.getLength() > 0) {
                Element el = (Element)nl.item(0);
                textVal = el.getFirstChild().getNodeValue();
            }

            return textVal;
    }


    /**
        * Calls getTextValue and returns a int value
        */
    private int getIntValue(Element ele, String tagName) {
            //in production application you would catch the exception
            return Integer.parseInt(getTextValue(ele,tagName));
    }
    
    
}
